import logging

from django.apps import AppConfig
from django.core.checks import register as register_check
from django.db import models
from watson import search as watson

from dojo.auditlog import configure_audit_system, register_django_pghistory_models
from dojo.checks import check_configuration_deduplication

logger = logging.getLogger(__name__)


class DojoAppConfig(AppConfig):
    name = "dojo"
    verbose_name = "Defect Dojo"

    def ready(self):
        # we need to initializer waston here because in models.py is to early if we want add extra fields to index
        # logger.info('ready(): initializing watson')
        # commented out ^ as it prints in manage.py dumpdata, docker logs and many other places
        # logger doesn't work yet at this stage

        # Watson doesn't have a way to let it index extra fields, so we have to explicitly list all the fields
        # to make it easier, we get the charfields/textfields from the model and then add our extra fields.
        # charfields/textfields are the fields that watson indexes by default (but we have to repeat here if we add extra fields)
        # and watson likes to have tuples instead of lists

        watson.register(self.get_model("Product"), fields=get_model_fields_with_extra(self.get_model("Product"), ("id", "prod_type__name")), store=("prod_type__name", ))

        watson.register(self.get_model("Test"), fields=get_model_fields_with_extra(self.get_model("Test"), ("id", "engagement__product__name")), store=("engagement__product__name", ))  # test_type__name?

        watson.register(self.get_model("Finding"), fields=get_model_fields_with_extra(self.get_model("Finding"), ("id", "url", "unique_id_from_tool", "test__engagement__product__name", "jira_issue__jira_key")),
                        store=("status", "jira_issue__jira_key", "test__engagement__product__name", "severity", "severity_display", "latest_note"))

        # some thoughts on Finding fields that are not indexed yet:
        # CWE can't be indexed as it is an integer

        # would endpoints be good to index? or would it clutter search results?
        # endpoints = models.ManyToManyField(Endpoint, blank=True)
        # endpoint_status = models.ManyToManyField(Endpoint_Status, blank=True, related_name='finding_endpoint_status')

        # index test name/title?
        # test = models.ForeignKey(Test, editable=False, on_delete=models.CASCADE)

        # index reporter name?
        # reporter = models.ForeignKey(User, editable=False, default=1, related_name='reporter', on_delete=models.CASCADE)
        # index notes?
        # notes = models.ManyToManyField(Notes, blank=True, editable=False)

        # index found_by?
        # found_by = models.ManyToManyField(Test_Type, editable=False)

        # exclude these to avoid cluttering?
        # sast_source_object = models.CharField(null=True, blank=True, max_length=500, help_text="Source object (variable, function...) of the attack vector")
        # sast_sink_object = models.CharField(null=True, blank=True, max_length=500, help_text="Sink object (variable, function...) of the attack vector")
        # sast_source_line = models.IntegerField(null=True, blank=True,
        #                            verbose_name="Line number",
        #                            help_text="Source line number of the attack vector")
        # sast_source_file_path = models.CharField(null=True, blank=True, max_length=4000, help_text="Source filepath of the attack vector")

        watson.register(self.get_model("Finding_Template"))
        watson.register(self.get_model("Endpoint"), store=("product__name", ))  # add product name also?
        watson.register(self.get_model("Engagement"), fields=get_model_fields_with_extra(self.get_model("Engagement"), ("id", "product__name")), store=("product__name", ))
        watson.register(self.get_model("App_Analysis"))
        watson.register(self.get_model("Vulnerability_Id"), store=("finding__test__engagement__product__name", ))

        # YourModel = self.get_model("YourModel")
        # watson.register(YourModel)

        register_check(check_configuration_deduplication, "dojo")

        # Load any signals here that will be ready for runtime
        # Importing the signals file is good enough if using the reciever decorator
        import dojo.announcement.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.benchmark.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.cred.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.endpoint.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.engagement.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.file_uploads.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.finding_group.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.notes.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.product.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.product_type.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.risk_acceptance.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.sla_config.helpers  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.tags_signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.test.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady
        import dojo.tool_product.signals  # noqa: PLC0415, F401 raised: AppRegistryNotReady

        # Configure audit system after all models are loaded
        # This must be done in ready() to avoid "Models aren't loaded yet" errors
        # Note: pghistory models are registered here (no database access), but trigger
        # enabling is handled via management command to avoid database access warnings
        register_django_pghistory_models()
        configure_audit_system()


def get_model_fields_with_extra(model, extra_fields=()):
    return get_model_fields(get_model_default_fields(model), extra_fields)


def get_model_fields(default_fields, extra_fields=()):
    return default_fields + extra_fields


def get_model_default_fields(model):
    return tuple(
        field.name for field in model._meta.fields if
        isinstance(field, models.CharField | models.TextField)
    )
